// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Contains integration tests for the generated `Convert` implementations.
//!
//! For generated code, we write hand-crafted integration tests *once* in the
//! project. The generated implementations for
//! `gaxi::prost::{FromProto, ToProto}` usually reference a type generated by
//! Protobuf, and these are `pub(crate)`. Therefore, the tests must be part of
//! the crate that contains them, and cannot be a separate integration tests
//! crate, or a file in the `tests/` directory.
//!
//! The strategy we follow is to write one test per category of generated code.
//! Exhaustive tests of each generated code quickly get repetitive. The most
//! sources of variation in these implementations are the type of fields. Such
//! as:
//!
//! - Simple primitive fields
//! - Enum fields
//! - Repeated fields
//! - Map fields
//! - OneOf fields
//! - Optional fields
//!
//! And then you need to consider variations on these, such as oneofs that
//! contain enums, or maps that contain enums.

#[cfg(test)]
mod tests {
    use crate::google;
    use crate::model;
    use gaxi::prost::FromProto;
    use gaxi::prost::ToProto;

    #[test]
    fn test_basic_fields() -> anyhow::Result<()> {
        let sidekick = gtype::model::LatLng::new()
            .set_latitude(12.5)
            .set_longitude(34.5);
        let proto = google::r#type::LatLng {
            latitude: 12.5,
            longitude: 34.5,
        };

        let got = sidekick.clone().to_proto()?;
        assert_eq!(got, proto);

        let got = proto.cnv()?;
        assert_eq!(got, sidekick);
        Ok(())
    }

    #[test]
    fn test_enum_field() -> anyhow::Result<()> {
        let sidekick = model::structured_query::FieldFilter::new()
            .set_op(model::structured_query::field_filter::Operator::Equal);
        let proto = google::firestore::v1::structured_query::FieldFilter {
            op: google::firestore::v1::structured_query::field_filter::Operator::Equal.into(),
            ..Default::default()
        };

        let got = sidekick.clone().to_proto()?;
        assert_eq!(got, proto);

        let got = proto.clone().cnv()?;
        assert_eq!(got, sidekick);
        Ok(())
    }

    #[test]
    fn test_optional_field() -> anyhow::Result<()> {
        let sidekick = model::structured_query::UnaryFilter::new().set_operand_type(
            model::structured_query::unary_filter::OperandType::Field(Box::new(
                model::structured_query::FieldReference::new().set_field_path("a.b.c"),
            )),
        );
        let proto = google::firestore::v1::structured_query::UnaryFilter {
            operand_type:
                google::firestore::v1::structured_query::unary_filter::OperandType::Field(
                    google::firestore::v1::structured_query::FieldReference {
                        field_path: "a.b.c".into(),
                    },
                )
                .into(),
            ..Default::default()
        };

        let got = sidekick.clone().to_proto()?;
        assert_eq!(got, proto);

        let got = proto.clone().cnv()?;
        assert_eq!(got, sidekick);
        Ok(())
    }

    #[test]
    fn test_oneof_null_value() -> anyhow::Result<()> {
        let sidekick = model::value::ValueType::NullValue(wkt::NullValue);
        let proto = google::firestore::v1::value::ValueType::NullValue(0);

        let got = sidekick.clone().to_proto()?;
        assert_eq!(got, proto);

        let got = proto.clone().cnv()?;
        assert_eq!(got, sidekick);
        Ok(())
    }

    #[test]
    fn test_oneof_primitive() -> anyhow::Result<()> {
        let sidekick = model::value::ValueType::BooleanValue(true);
        let proto = google::firestore::v1::value::ValueType::BooleanValue(true);

        let got = sidekick.clone().to_proto()?;
        assert_eq!(got, proto);

        let got = proto.clone().cnv()?;
        assert_eq!(got, sidekick);
        Ok(())
    }

    #[test]
    fn test_oneof_message() -> anyhow::Result<()> {
        let sidekick =
            model::value::ValueType::TimestampValue(Box::new(wkt::Timestamp::clamp(123, 456)));
        let proto =
            google::firestore::v1::value::ValueType::TimestampValue(prost_types::Timestamp {
                seconds: 123,
                nanos: 456,
            });

        let got = sidekick.clone().to_proto()?;
        assert_eq!(got, proto);

        let got = proto.clone().cnv()?;
        assert_eq!(got, sidekick);
        Ok(())
    }

    #[test]
    fn test_message_repeated() -> anyhow::Result<()> {
        let sidekick =
            model::value::ValueType::ArrayValue(Box::new(model::ArrayValue::new().set_values([
                model::Value::new().set_string_value("abc"),
                model::Value::new().set_integer_value(123),
                model::Value::new().set_double_value(1234.5),
            ])));
        let proto = google::firestore::v1::value::ValueType::ArrayValue(
            google::firestore::v1::ArrayValue {
                values: vec![
                    google::firestore::v1::Value {
                        value_type: google::firestore::v1::value::ValueType::StringValue(
                            "abc".into(),
                        )
                        .into(),
                    },
                    google::firestore::v1::Value {
                        value_type: google::firestore::v1::value::ValueType::IntegerValue(123)
                            .into(),
                    },
                    google::firestore::v1::Value {
                        value_type: google::firestore::v1::value::ValueType::DoubleValue(1234.5)
                            .into(),
                    },
                ],
            },
        );

        let got = sidekick.clone().to_proto()?;
        assert_eq!(got, proto);

        let got = proto.clone().cnv()?;
        assert_eq!(got, sidekick);
        Ok(())
    }

    #[test]
    fn test_message_map() -> anyhow::Result<()> {
        use std::collections::HashMap;
        let sidekick =
            model::value::ValueType::MapValue(Box::new(model::MapValue::new().set_fields([
                ("string", model::Value::new().set_string_value("abc")),
                ("integer", model::Value::new().set_integer_value(123)),
                ("double", model::Value::new().set_double_value(1234.5)),
            ])));
        let proto =
            google::firestore::v1::value::ValueType::MapValue(google::firestore::v1::MapValue {
                fields: HashMap::from_iter([
                    (
                        "string".to_string(),
                        google::firestore::v1::Value {
                            value_type: google::firestore::v1::value::ValueType::StringValue(
                                "abc".into(),
                            )
                            .into(),
                        },
                    ),
                    (
                        "integer".to_string(),
                        google::firestore::v1::Value {
                            value_type: google::firestore::v1::value::ValueType::IntegerValue(123)
                                .into(),
                        },
                    ),
                    (
                        "double".to_string(),
                        google::firestore::v1::Value {
                            value_type: google::firestore::v1::value::ValueType::DoubleValue(
                                1234.5,
                            )
                            .into(),
                        },
                    ),
                ]),
            });

        let got = sidekick.clone().to_proto()?;
        assert_eq!(got, proto);

        let got = proto.clone().cnv()?;
        assert_eq!(got, sidekick);
        Ok(())
    }
}
